using System;
using System.Collections.Generic;
using System.Text;
using AUSRIS.Reports.Utilities.Deployment.Entities;
using AUSRIS.Reports.Utilities.Deployment.FileSystem;
using AUSRIS.Reports.Utilities.Deployment.Logging;
using AUSRIS.Reports.Utilities.Deployment.Target;
using AUSRIS.Reports.Utilities.Deployment.Configuration;

namespace AUSRIS.Reports.Utilities.Deployment
{
	/// <summary>
	/// This implementation of <see cref="IPublisher"/>
	/// checks out a group of files (specified by module-name 
	/// in the <see cref="DeploymentSet"/>) and then 
	/// compares each file's revision with what was
	/// previously published and publishes the file
	/// if neccessary
	/// </summary>
	public class BatchPublisher : IPublisher
	{
		#region Private Fields

		private bool error;
		private IReportServer reportServer;
		private IRepository repository;
		private ILogger logger;
		private IConfigurationDatabase configurationDatabase;
		private DeploymentSet currentDeploymentSet;
		private IList<Report> publishedReportLog;
		private IList<Report> skippedReportLog;
		#endregion

		#region Public Properties

		public ILogger Logger
		{
			get
			{
				return logger;
			}
			set
			{
				logger = value;
			}
		}

		public IRepository Repository
		{
			get
			{
				return repository;
			}
			set
			{
				repository = value;
			}
		}

		public IReportServer ReportServer
		{
			get
			{
				return reportServer;
			}
			set
			{
				reportServer = value;
			}
		}

		public IConfigurationDatabase ConfigurationDatabase
		{
			get
			{
				return configurationDatabase;
			}
			set
			{
				configurationDatabase = value;
			}
		}

		#endregion

		#region Public Methods

		public void LoadDeploymentSet(string name)
		{
			this.skippedReportLog = new List<Report>();
			this.publishedReportLog = new List<Report>();
			this.error = false;
			this.Logger.LogMessage("Loading Deployment Set '{0}'", name);
			this.currentDeploymentSet = this.ConfigurationDatabase.GetDeploymentSet(name);
			if ( this.currentDeploymentSet == null )
			{
				this.error = true;
				this.Logger.LogException(new Exception("Could not load Deployment Set: " + name));
			}
		}

		public void Publish()
		{
			if ( this.currentDeploymentSet == null )
			{
				throw new InvalidOperationException("No Deployment Set Loaded");
			}
			this.CheckReportServer();
			this.GetLocalFileCopies();
			this.BeginTransaction();
			this.CleanupMissingItems();
			this.PublishFolders();
			this.PublishDatasources();
			this.PublishFiles();
			this.CommitTransaction();
			this.UpdateReportParameters();
			this.LogCompletionMessages();
			this.Logger.Flush();
		}

		#endregion

		#region Private Methods

		private void GetLocalFileCopies()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Checking out local copies of {0}", this.currentDeploymentSet.ModuleName);
					this.Repository.CheckoutModule(this.currentDeploymentSet.ModuleName, this.currentDeploymentSet.VersionTag);
				}
				catch ( Exception ex )
				{
					this.error = true;
					this.Logger.LogException(ex);
				}
			}
		}

		private void LogCompletionMessages()
		{
			if ( !error )
			{
				this.Logger.LogHeader("Publish Executed Successfully");
				this.Logger.LogMessage("Processed {0} report(s)", this.skippedReportLog.Count + this.publishedReportLog.Count);
				this.Logger.LogMessage("Published {0} report(s)", this.publishedReportLog.Count);
				this.Logger.LogMessage("Skipped {0} report(s)", this.skippedReportLog.Count);
				if ( this.publishedReportLog.Count > 0 )
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("The following reports were published:");
					foreach ( Report report in this.publishedReportLog )
					{
						this.Logger.LogMessage(report.Path);
					}
				}
			}
		}

		private void CheckReportServer()
		{
			if ( !this.error )
			{
				this.Logger.LogMessage("Checking report server {0}", this.ReportServer.ToString());
				try
				{
					if ( !this.ReportServer.CanConnect() )
					{
						this.Logger.LogMessage("Could not connect to report server");
						this.error = true;
					}
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.error = true;
				}
			}
		}

		private void BeginTransaction()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Beginning Transaction");
					this.ReportServer.BeginTransaction();
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
				}
			}
		}

		private void CommitTransaction()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Committing Transaction");
					this.ReportServer.CommitTransaction();
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.Cancel();
				}
			}
		}

		private void Cancel()
		{
			this.error = true;

			this.Logger.LogNewLine();
			this.Logger.LogMessage("Rolling Back Transaction");

			this.ReportServer.RollbackTransaction();
			
			this.Logger.LogNewLine();
			this.Logger.LogMessage("Publish Failed");
		}

		private void CleanupMissingItems()
		{
			if ( !this.error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Cleaning up items on server");
					IList<Folder> topFolders = this.currentDeploymentSet.Folders.GetTopFolders();
					foreach ( Folder topFolder in topFolders )
					{
						this.CleanFolder(topFolder);
					}
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.Cancel();
				}
			}
		}

		private void CleanFolder(Folder folder)
		{
			Folder deploymentFolder = null;
			if(this.currentDeploymentSet.Folders.ContainsKey(folder.Path))
			{
				deploymentFolder = this.currentDeploymentSet.Folders[folder.Path];
			}
			if ( deploymentFolder == null )
			{
				this.Logger.LogMessage("Removing Folder '{0}'", folder.Path);
				this.ReportServer.DeleteItem(folder);
			}
			else
			{
				foreach ( CatalogFile file in this.ReportServer.GetFiles(folder) )
				{
					if ( !deploymentFolder.ContainsFile(file) )
					{
						this.Logger.LogMessage("Removing File '{0}'", file.Path);
						this.ReportServer.DeleteItem(file);
					}
				}
				foreach ( Folder subFolder in this.ReportServer.GetFolders(folder) )
				{
					this.CleanFolder(subFolder);
				}
			}
		}

		private void PublishFolders()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Publishing Folders");
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					try
					{
						if ( !this.ReportServer.ItemExists(folder) )
						{
							this.ReportServer.PublishFolder(folder);
							this.Logger.LogMessage("Folder created '{0}' at '{1}'", folder.Name, folder.ParentPath);
							this.PublishPolicies(folder);
						}
					}
					catch ( Exception ex )
					{
						this.Logger.LogException( ex);
						this.Cancel();
						break;
					}
				}
			}
		}

		private void PublishDatasources()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					if ( folder.Datasources.Count > 0 )
					{
						this.Logger.LogMessage("Publishing DataSources in '{0}'", folder.Path);
						foreach ( DataSource dataSource in folder.Datasources )
						{
							try
							{
								if ( !dataSource.Overwrite && this.ReportServer.ItemExists(dataSource) )
								{
									this.Logger.LogMessage("DataSouce '{0}' already published, not overwriting", dataSource.Name);
								}
								else
								{
									this.Logger.LogMessage("Publishing '{0}'", dataSource.Name);
									this.ReportServer.PublishDataSource(dataSource);
									this.Logger.LogMessage("DataSource published '{0}'", dataSource.Name);
								}
							}
							catch ( Exception ex )
							{
								this.Logger.LogException(ex);
								this.Cancel();
								break;
							}
						}
					}
				}
			}
		}

		private void PublishFiles()
		{
			if ( !error )
			{
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					if ( !error )
					{
						if ( folder.Files.Count > 0 )
						{
							this.Logger.LogHeader("Publishing Files in '{0}'", folder.Path);
							foreach ( CatalogFile file in folder.Files )
							{
								this.Logger.LogNewLine();
								this.Logger.LogMessage("Getting revision for '{0}'", file.Name);
								try
								{
									this.Repository.SetRevision(file);
								}
								catch ( Exception ex )
								{
									this.Logger.LogException(ex);
									this.Cancel();
									break;
								}
								try
								{
									if ( this.FileIsPublished(folder, file) )
									{
										this.Logger.LogMessage("File '{0}', Revision '{1}' is already published", file.Name, file.Revision);
										this.LogItem(file, false);
									}
									else
									{
										this.Logger.LogMessage("Publishing '{0}'", file.Name);
										this.PublishFile(folder, file);
										this.PublishPolicies(file);
										this.Logger.LogMessage("Published File '{0}'", file.Name);
										this.LogItem(file, true);
									}
								}
								catch ( Exception ex )
								{
									this.Logger.LogException(ex);
									this.Cancel();
									break;
								}
							}
						}
					}
					
				}
			}
		}

		private void PublishPolicies(DeploymentItem item)
		{
			if ( !error )
			{
				if ( item.Policies.Count > 0 )
				{
					this.Logger.LogMessage("Publishing Policies for '{0}'", item.Name);
					this.ReportServer.PublishPolicies(item);
					this.Logger.LogMessage("Policies set for '{0}'", item.Name);
				}
			}
		}

		private bool FileIsPublished(Folder folder, CatalogFile file)
		{
			Report report = file as Report;
			if ( report != null )
			{
				string lastRevisionPublished = this.ReportServer.GetReportRevision(report);
				return lastRevisionPublished == report.Revision;
			}
			else
			{
				return this.ReportServer.ItemExists(file);
			}
		}

		private void UpdateReportParameters()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Updating report parameters on server");
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					foreach ( CatalogFile file in folder.Files )
					{
						Report report = file as Report;
						if ( report != null )
						{
							if (  this.publishedReportLog.Contains(report) ) //this.reportLog.ContainsKey(report) && this.reportLog[report] == true )
							{
								try
								{
									this.ReportServer.UpdateReportParameters(report);
								}
								catch ( Exception ex )
								{
									this.Logger.LogException(ex);
								}
								this.Logger.LogMessage("Updated parameters for '{0}'", report.Name);
							}
						}
					}
				}
			}
		}

		private void PublishFile(Folder folder, CatalogFile file)
		{
			if ( file.GetType().Equals(typeof(Report)) )
			{
				this.ReportServer.PublishReport((Report)file);
			}
			else if ( file.GetType().Equals(typeof(Resource)) )
			{
				this.ReportServer.PublishResource((Resource) file);
			}
		}

		private void LogItem(DeploymentItem item, bool wasPublished)
		{
			if ( item.GetType().Equals(typeof(Report)) )
			{
				if ( wasPublished )
				{
					this.publishedReportLog.Add((Report) item);
				}
				else
				{
					this.skippedReportLog.Add((Report) item);
				}
			}
		}

		#endregion
	}
}